diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ede393e..ea59836 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,9 @@ jobs: rust-tests: name: Run tests runs-on: ubuntu-latest + strategy: + matrix: + toolchain: ["1.67"] steps: - name: Checkout uses: actions/checkout@v3 @@ -64,11 +67,15 @@ jobs: - name: Install rust uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: ${{ matrix.toolchain }} profile: minimal + override: true - uses: Swatinem/rust-cache@v2 + - name: Regenerate test data + run: ./scripts/regenerate_test_rustdocs.sh +${{ matrix.toolchain }} + - name: compile run: cargo test --no-run diff --git a/.gitignore b/.gitignore index 96f9509..7369681 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,15 @@ # Generated by Cargo # will have compiled files and executables -/target/ +**/target/ # Local configuration for the project. .cargo/ # These are backup files generated by rustfmt **/*.rs.bk + +# local-only data +localdata/ + +# lockfiles of test crates +test_crates/**/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 3145586..c2f3660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + [[package]] name = "async-graphql-parser" version = "2.11.3" @@ -517,6 +523,9 @@ dependencies = [ name = "trustfall-rustdoc-adapter" version = "22.4.0" dependencies = [ + "anyhow", + "itertools", + "maplit", "rustdoc-types", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index f5f2b8b..6bbf789 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,8 @@ trustfall_core = "0.1.1" rustdoc-types = "0.18.0" [dev-dependencies] +anyhow = "1.0.58" +itertools = "0.10.5" serde_json = "1.0.85" serde = { version = "1.0.145", features = ["derive"] } +maplit = "1.0.2" diff --git a/scripts/regenerate_test_rustdocs.sh b/scripts/regenerate_test_rustdocs.sh new file mode 100755 index 0000000..5852e9f --- /dev/null +++ b/scripts/regenerate_test_rustdocs.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Fail on first error, on undefined variables, and on failures in pipelines. +set -euo pipefail + +export CARGO_TARGET_DIR=/tmp/test_crates +RUSTDOC_OUTPUT_DIR="$CARGO_TARGET_DIR/doc" +TOPLEVEL="$(git rev-parse --show-toplevel)" +TARGET_DIR="$TOPLEVEL/localdata/test_data" + +# Allow setting an explicit toolchain, like +nightly or +beta. +set +u +TOOLCHAIN="$1" +set -u +echo "Generating rustdoc with: $(cargo $TOOLCHAIN --version)" +RUSTDOC_CMD="cargo $TOOLCHAIN rustdoc" + +# Run rustdoc on test_crates/*/ +for crate_path in $(find "$TOPLEVEL/test_crates/" -maxdepth 1 -mindepth 1 -type d); do + # Removing path prefix, leaving only the directory name without forward slashes + crate=${crate_path#"$TOPLEVEL/test_crates/"} + + if [[ -f "$TOPLEVEL/test_crates/$crate/Cargo.toml" ]]; then + echo "Generating: $crate" + + pushd "$TOPLEVEL/test_crates/$crate" + RUSTC_BOOTSTRAP=1 $RUSTDOC_CMD -- -Zunstable-options --document-private-items --document-hidden-items --output-format=json + mkdir -p "$TARGET_DIR/$crate" + mv "$RUSTDOC_OUTPUT_DIR/$crate.json" "$TARGET_DIR/$crate/rustdoc.json" + popd + fi +done + +unset CARGO_TARGET_DIR diff --git a/src/indexed_crate.rs b/src/indexed_crate.rs index c381edb..9eb8c24 100644 --- a/src/indexed_crate.rs +++ b/src/indexed_crate.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use rustdoc_types::{Crate, Id, Item, Visibility}; +use rustdoc_types::{Crate, GenericArgs, Id, Item, Typedef, Visibility}; #[derive(Debug, Clone)] pub struct IndexedCrate<'a> { @@ -12,19 +12,33 @@ pub struct IndexedCrate<'a> { impl<'a> IndexedCrate<'a> { pub fn new(crate_: &'a Crate) -> Self { - let visibility_forest = calculate_visibility_forest(crate_); - Self { inner: crate_, - visibility_forest, + visibility_forest: compute_parent_ids_for_public_items(crate_) + .into_iter() + .map(|(key, values)| { + // Ensure a consistent order, since queries can observe this order directly. + let mut values: Vec<_> = values.into_iter().collect(); + values.sort_unstable_by_key(|x| &x.0); + (key, values) + }) + .collect(), } } + /// Return all the paths (as Vec<&'a str> of component names, joinable with "::") + /// with which the given item can be imported from this crate. pub fn publicly_importable_names(&self, id: &'a Id) -> Vec> { let mut result = vec![]; if self.inner.index.contains_key(id) { - self.collect_publicly_importable_names(id, &mut vec![], &mut result); + let mut already_visited_ids = Default::default(); + self.collect_publicly_importable_names( + id, + &mut already_visited_ids, + &mut vec![], + &mut result, + ); } result @@ -33,118 +47,977 @@ impl<'a> IndexedCrate<'a> { fn collect_publicly_importable_names( &self, next_id: &'a Id, + already_visited_ids: &mut HashSet<&'a Id>, stack: &mut Vec<&'a str>, output: &mut Vec>, ) { + if !already_visited_ids.insert(next_id) { + // We found a cycle, and we've already processed this item. + // Nothing more to do here. + return; + } + let item = &self.inner.index[next_id]; - if let Some(item_name) = item.name.as_deref() { - stack.push(item_name); - } else { - assert!( - matches!(item.inner, rustdoc_types::ItemEnum::Import(..)), - "{item:?}" - ); + let (push_name, popped_name) = match &item.inner { + rustdoc_types::ItemEnum::Import(import_item) => { + 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) + } else { + // Use the name of the imported item, since it might be renaming + // the item being imported. + let push_name = Some(import_item.name.as_str()); + + // The imported item may be renamed here, so pop it from the stack. + let popped_name = Some(stack.pop().expect("no name to pop")); + + (push_name, popped_name) + } + } + rustdoc_types::ItemEnum::Typedef(..) => { + // Use the typedef name instead of the underlying item's own name, + // since it might be renaming the underlying item. + let push_name = Some(item.name.as_deref().expect("typedef had no name")); + + // If there is an underlying item, pop it from the stack + // since it may be renamed here. + let popped_name = stack.pop(); + + (push_name, popped_name) + } + _ => (item.name.as_deref(), None), + }; + + // Push the new name onto the stack, if there is one. + if let Some(pushed_name) = push_name { + stack.push(pushed_name); + } + + self.collect_publicly_importable_names_inner(next_id, already_visited_ids, stack, output); + + // Undo any changes made to the stack, returning it to its pre-recursion state. + if let Some(pushed_name) = push_name { + let recovered_name = stack.pop().expect("there was nothing to pop"); + assert_eq!(pushed_name, recovered_name); + } + if let Some(popped_name) = popped_name { + stack.push(popped_name); } + // We're leaving this item. Remove it from the visited set. + let removed = already_visited_ids.remove(next_id); + assert!(removed); + } + + fn collect_publicly_importable_names_inner( + &self, + next_id: &'a Id, + already_visited_ids: &mut HashSet<&'a Id>, + stack: &mut Vec<&'a str>, + output: &mut Vec>, + ) { if next_id == &self.inner.root { let final_name = stack.iter().rev().copied().collect(); output.push(final_name); } else if let Some(visible_parents) = self.visibility_forest.get(next_id) { for parent_id in visible_parents.iter().copied() { - self.collect_publicly_importable_names(parent_id, stack, output); + self.collect_publicly_importable_names( + parent_id, + already_visited_ids, + stack, + output, + ); } } - - if let Some(item_name) = item.name.as_deref() { - let popped_item = stack.pop().expect("stack was unexpectedly empty"); - assert_eq!(item_name, popped_item); - } } } -fn calculate_visibility_forest(crate_: &Crate) -> HashMap<&Id, Vec<&Id>> { +fn compute_parent_ids_for_public_items(crate_: &Crate) -> HashMap<&Id, HashSet<&Id>> { let mut result = Default::default(); let root_id = &crate_.root; if let Some(root_module) = crate_.index.get(root_id) { if root_module.visibility == Visibility::Public { - collect_public_items(crate_, &mut result, root_module, None); + let mut currently_visited_items = Default::default(); + visit_root_reachable_public_items( + crate_, + &mut result, + &mut currently_visited_items, + root_module, + None, + ); } } result } -fn collect_public_items<'a>( +/// Collect all public items that are reachable from the crate root and record their parent Ids. +fn visit_root_reachable_public_items<'a>( crate_: &'a Crate, - pub_items: &mut HashMap<&'a Id, Vec<&'a Id>>, + parents: &mut HashMap<&'a Id, HashSet<&'a Id>>, + currently_visited_items: &mut HashSet<&'a Id>, item: &'a Item, parent_id: Option<&'a Id>, ) { match item.visibility { - // Some impls and methods have default visibility: - // they are visible only if the type to which they belong is visible. - // However, we don't recurse into non-public items with this function, so - // reachable items with default visibility must be public. - Visibility::Public | Visibility::Default => { - let parents = pub_items.entry(&item.id).or_default(); - if let Some(parent_id) = parent_id { - parents.push(parent_id); + Visibility::Crate | Visibility::Restricted { .. } => { + // This item is not public, so we don't need to process it. + return; + } + Visibility::Public => {} // Public item, keep going. + Visibility::Default => { + // Enum variants, and some impls and methods have default visibility: + // they are visible only if the type to which they belong is visible. + // However, we don't recurse into non-public items with this function, so + // reachable items with default visibility must be public. + } + } + + let item_parents = parents.entry(&item.id).or_default(); + if let Some(parent_id) = parent_id { + item_parents.insert(parent_id); + } + + if !currently_visited_items.insert(&item.id) { + // We found a cycle in the import graph, and we've already processed this item. + // Nothing more to do here. + return; + } + + let next_parent_id = Some(&item.id); + match &item.inner { + rustdoc_types::ItemEnum::Module(m) => { + for inner in m.items.iter().filter_map(|id| crate_.index.get(id)) { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + inner, + next_parent_id, + ); } + } + rustdoc_types::ItemEnum::Import(imp) => { + // Imports of modules, and glob imports of enums, + // import the *contents* of the pointed-to item rather than the item itself. + if let Some(imported_item) = imp.id.as_ref().and_then(|id| crate_.index.get(id)) { + if imp.glob { + // Glob imports point directly to the contents of the pointed-to module. + // For each item in that module, the import's parent becomes its parent as well. + let next_parent_id = parent_id; - let next_parent_id = Some(&item.id); - match &item.inner { - rustdoc_types::ItemEnum::Module(m) => { - for inner in m.items.iter().filter_map(|id| crate_.index.get(id)) { - collect_public_items(crate_, pub_items, inner, next_parent_id); + let inner_ids = match &imported_item.inner { + rustdoc_types::ItemEnum::Module(mod_item) => &mod_item.items, + rustdoc_types::ItemEnum::Enum(enum_item) => &enum_item.variants, + _ => unreachable!( + "found a glob import of an unexpected kind of item: \ + {imp:?} {imported_item:?}" + ), + }; + for inner_id in inner_ids { + if let Some(item) = crate_.index.get(inner_id) { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + item, + next_parent_id, + ); + } } + } else { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + imported_item, + next_parent_id, + ); } - rustdoc_types::ItemEnum::Import(imp) => { - // TODO: handle glob imports (`pub use foo::bar::*`) here. - if let Some(item) = imp.id.as_ref().and_then(|id| crate_.index.get(id)) { - collect_public_items(crate_, pub_items, item, next_parent_id); - } + } + } + rustdoc_types::ItemEnum::Struct(struct_) => { + let field_ids_iter: Box> = match &struct_.kind { + rustdoc_types::StructKind::Unit => Box::new(std::iter::empty()), + rustdoc_types::StructKind::Tuple(field_ids) => { + Box::new(field_ids.iter().filter_map(|x| x.as_ref())) } - rustdoc_types::ItemEnum::Struct(struct_) => { - let field_ids_iter: Box> = match &struct_.kind { - rustdoc_types::StructKind::Unit => Box::new(std::iter::empty()), - rustdoc_types::StructKind::Tuple(field_ids) => { - Box::new(field_ids.iter().filter_map(|x| x.as_ref())) - } - rustdoc_types::StructKind::Plain { fields, .. } => Box::new(fields.iter()), - }; + rustdoc_types::StructKind::Plain { fields, .. } => Box::new(fields.iter()), + }; + + for inner in field_ids_iter + .chain(struct_.impls.iter()) + .filter_map(|id| crate_.index.get(id)) + { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + inner, + next_parent_id, + ); + } + } + rustdoc_types::ItemEnum::Enum(enum_) => { + for inner in enum_ + .variants + .iter() + .chain(enum_.impls.iter()) + .filter_map(|id| crate_.index.get(id)) + { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + inner, + next_parent_id, + ); + } + } + rustdoc_types::ItemEnum::Trait(trait_) => { + for inner in trait_.items.iter().filter_map(|id| crate_.index.get(id)) { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + inner, + next_parent_id, + ); + } + } + rustdoc_types::ItemEnum::Impl(impl_) => { + for inner in impl_.items.iter().filter_map(|id| crate_.index.get(id)) { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + inner, + next_parent_id, + ); + } + } + rustdoc_types::ItemEnum::Typedef(ty) => { + // We're interested in type aliases that are specifically used to rename types: + // `pub type Foo = Bar` + // If the underlying type is generic, it's only a valid renaming if the typedef + // is also generic in all the same parameters. + // + // The Rust compiler ignores `where` bounds on typedefs, so we ignore them too. + if let Some(reexport_target) = get_typedef_equivalent_reexport_target(crate_, ty) { + visit_root_reachable_public_items( + crate_, + parents, + currently_visited_items, + reexport_target, + next_parent_id, + ); + } + } + _ => { + // No-op, no further items within to consider. + } + } + + // We are leaving this item. Remove it from the visited set. + let removed = currently_visited_items.remove(&item.id); + assert!(removed); +} + +/// Type aliases can sometimes be equivalent to a regular `pub use` re-export: +/// `pub type Foo = crate::Bar` is an example, equivalent to `pub use crate::Bar`. +/// +/// If the underlying type has generic parameters, the type alias must include +/// all the same generic parameters in the same order. +/// `pub type Foo = crate::Bar` is *not* equivalent to `pub use crate::Bar`. +fn get_typedef_equivalent_reexport_target<'a>( + crate_: &'a Crate, + ty: &'a Typedef, +) -> Option<&'a Item> { + println!("{ty:?}"); + if let rustdoc_types::Type::ResolvedPath(resolved_path) = &ty.type_ { + let underlying = crate_.index.get(&resolved_path.id)?; - for inner in field_ids_iter - .chain(struct_.impls.iter()) - .filter_map(|id| crate_.index.get(id)) - { - collect_public_items(crate_, pub_items, inner, next_parent_id); + if let Some(GenericArgs::AngleBracketed { args, bindings }) = resolved_path.args.as_deref() + { + if !bindings.is_empty() { + // The type alias specifies some of the underlying type's generic parameters. + // This is not equivalent to a re-export. + return None; + } + + let underlying_generics = match &underlying.inner { + rustdoc_types::ItemEnum::Struct(struct_) => &struct_.generics, + rustdoc_types::ItemEnum::Enum(enum_) => &enum_.generics, + rustdoc_types::ItemEnum::Trait(trait_) => &trait_.generics, + rustdoc_types::ItemEnum::Union(union_) => &union_.generics, + rustdoc_types::ItemEnum::Typedef(ty) => &ty.generics, + _ => unreachable!("unexpected underlying item kind: {underlying:?}"), + }; + + // For the typedef to be equivalent to a re-export, all of the following must hold: + // - The typedef has the same number of generic parameters as the underlying. + // - All underlying generic parameters are available on the typedef, + // are of the same kind, in the same order, with the same defaults. + if ty.generics.params.len() != args.len() { + return None; + } + assert_eq!( + underlying_generics.params.len(), + args.len(), + "underlying type expected different number of generic args than were provided: \ + {underlying_generics:?} {args:?}\nitem = {underlying:?}" + ); + for (ty_generic, (underlying_param, arg_generic)) in ty + .generics + .params + .iter() + .zip(underlying_generics.params.iter().zip(args.iter())) + { + let arg_generic_name = match arg_generic { + rustdoc_types::GenericArg::Lifetime(name) => name.as_str(), + rustdoc_types::GenericArg::Type(rustdoc_types::Type::Generic(t)) => t.as_str(), + rustdoc_types::GenericArg::Type(_) => return None, + rustdoc_types::GenericArg::Const(c) => { + // Nominally, this is the const expression, not the const generic's name. + // However, except for pathological edge cases, if the expression is not + // simply the const generic parameter itself, then the type isn't the same. + // + // An example pathological case where this isn't the case is: + // `pub type Foo = Underlying;` + // Detecting that this is the same expression requires that one of + // rustdoc or our code do const-evaluation here. + // + // Const expressions like this are currently only on nightly, + // so we can't test them on stable Rust at the moment. + // + // TODO: revisit this decision when const expressions in types are stable + c.expr.as_str() } + rustdoc_types::GenericArg::Infer => return None, + }; + if ty_generic.name.as_str() != arg_generic_name { + // The typedef params are not in the same order as the underlying type's. + return None; } - rustdoc_types::ItemEnum::Enum(enum_) => { - for inner in enum_ - .variants - .iter() - .chain(enum_.impls.iter()) - .filter_map(|id| crate_.index.get(id)) - { - collect_public_items(crate_, pub_items, inner, next_parent_id); + + match (&ty_generic.kind, &underlying_param.kind) { + ( + rustdoc_types::GenericParamDefKind::Lifetime { .. }, + rustdoc_types::GenericParamDefKind::Lifetime { .. }, + ) => { + // Typedefs cannot have "outlives" relationships on their lifetimes, + // so there's nothing further to compare here. So far, it's a match. } - } - rustdoc_types::ItemEnum::Trait(trait_) => { - for inner in trait_.items.iter().filter_map(|id| crate_.index.get(id)) { - collect_public_items(crate_, pub_items, inner, next_parent_id); + ( + rustdoc_types::GenericParamDefKind::Type { + default: ty_default, + .. + }, + rustdoc_types::GenericParamDefKind::Type { + default: underlying_default, + .. + }, + ) => { + // If the typedef doesn't have the same default values for its generics, + // then it isn't equivalent to the underlying and so isn't a re-export. + if ty_default != underlying_default { + // The defaults have changed. + return None; + } + // We don't care about the other fields. + // Generic bounds on typedefs are ignored by rustc and generate a lint. + } + ( + rustdoc_types::GenericParamDefKind::Const { + type_: ty_type, + default: ty_default, + }, + rustdoc_types::GenericParamDefKind::Const { + type_: underlying_type, + default: underlying_default, + }, + ) => { + // If the typedef doesn't have the same default values for its generics, + // then it isn't equivalent to the underlying and so isn't a re-export. + // + // Similarly, if it is in any way possible to change the const generic type, + // that makes the typedef not a re-export anymore. + if ty_default != underlying_default || ty_type != underlying_type { + // The generic type or its default has changed. + return None; + } + } + _ => { + // Not the same kind of generic parameter. + return None; } } - rustdoc_types::ItemEnum::Impl(impl_) => { - for inner in impl_.items.iter().filter_map(|id| crate_.index.get(id)) { - collect_public_items(crate_, pub_items, inner, next_parent_id); + } + } + + Some(underlying) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use std::fs::read_to_string; + + use anyhow::Context; + use rustdoc_types::Crate; + + #[derive(serde::Deserialize)] + struct RustdocFormatVersion { + format_version: u32, + } + + fn detect_rustdoc_format_version(file_data: &str) -> anyhow::Result { + let version = serde_json::from_str::(file_data) + .with_context(|| "file does not appear to be a rustdoc JSON format".to_string())?; + + Ok(version.format_version) + } + + fn load_pregenerated_rustdoc(crate_name: &str) -> Crate { + let path = format!("./localdata/test_data/{crate_name}/rustdoc.json"); + let content = read_to_string(&path) + .with_context(|| format!("Could not load {path} file, did you forget to run ./scripts/regenerate_test_rustdocs.sh ?")) + .expect("failed to load rustdoc"); + serde_json::from_str(&content) + .with_context(|| { + let format_version = detect_rustdoc_format_version(&content); + match format_version { + Ok(format_version) => { + format!( + "Failed to parse {path} file: it is rustdoc version {format_version} but expected {}. \ + Did you forget to run ./scripts/regenerate_test_rustdocs.sh ?", + rustdoc_types::FORMAT_VERSION, + ) + } + Err(..) => { + format!( + "Failed to parse {path} file: it didn't seem to be valid rustdoc JSON. \ + Did you forget to run ./scripts/regenerate_test_rustdocs.sh ?" + ) } } - _ => { - // No-op, no further items within to consider. + }).expect("failed to parse rustdoc JSON") + } + + mod reexports { + use std::collections::{BTreeMap, BTreeSet}; + + use itertools::Itertools; + use maplit::{btreemap, btreeset}; + + use crate::IndexedCrate; + + use super::load_pregenerated_rustdoc; + + fn assert_exported_items_match( + test_crate: &str, + expected_items: &BTreeMap<&str, BTreeSet<&str>>, + ) { + let rustdoc = load_pregenerated_rustdoc(test_crate); + let indexed_crate = IndexedCrate::new(&rustdoc); + + println!("vis: {:#?}", indexed_crate.visibility_forest); + + for (&expected_item_name, expected_importable_paths) in expected_items { + assert!( + !expected_item_name.contains(':'), + "only direct item names can be checked at the moment: {expected_item_name}" + ); + + let item_id_candidates = rustdoc + .index + .iter() + .filter_map(|(id, item)| { + (item.name.as_deref() == Some(expected_item_name)).then_some(id) + }) + .collect_vec(); + if item_id_candidates.len() != 1 { + panic!( + "Expected to find exactly one item with name {expected_item_name}, \ + but found these matching IDs: {item_id_candidates:?}" + ); } + let item_id = item_id_candidates[0]; + let actual_items: Vec<_> = indexed_crate + .publicly_importable_names(item_id) + .into_iter() + .map(|components| components.into_iter().join("::")) + .collect(); + let deduplicated_actual_items: BTreeSet<_> = + actual_items.iter().map(|x| x.as_str()).collect(); + assert_eq!( + actual_items.len(), + deduplicated_actual_items.len(), + "duplicates found: {actual_items:?}" + ); + + assert_eq!(expected_importable_paths, &deduplicated_actual_items); } } - Visibility::Crate | Visibility::Restricted { .. } => {} + + #[test] + fn pub_inside_pub_crate_mod() { + let test_crate = "pub_inside_pub_crate_mod"; + let expected_items = btreemap! { + "Foo" => btreeset![], + "Bar" => btreeset![ + "pub_inside_pub_crate_mod::Bar", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn reexport() { + let test_crate = "reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + "reexport::foo", + "reexport::inner::foo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn reexport_from_private_module() { + let test_crate = "reexport_from_private_module"; + let expected_items = btreemap! { + "foo" => btreeset![ + "reexport_from_private_module::foo", + ], + "Bar" => btreeset![ + "reexport_from_private_module::Bar", + ], + "Baz" => btreeset![ + "reexport_from_private_module::nested::Baz", + ], + "quux" => btreeset![ + "reexport_from_private_module::quux", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn renaming_reexport() { + let test_crate = "renaming_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + "renaming_reexport::bar", + "renaming_reexport::inner::foo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn renaming_mod_reexport() { + let test_crate = "renaming_mod_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + "renaming_mod_reexport::inner::a::foo", + "renaming_mod_reexport::inner::b::foo", + "renaming_mod_reexport::direct::foo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn glob_reexport() { + let test_crate = "glob_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + "glob_reexport::foo", + "glob_reexport::inner::foo", + ], + "Bar" => btreeset![ + "glob_reexport::Bar", + "glob_reexport::inner::Bar", + ], + "nested" => btreeset![ + "glob_reexport::nested", + ], + "Baz" => btreeset![ + "glob_reexport::Baz", + ], + "First" => btreeset![ + "glob_reexport::First", + "glob_reexport::Baz::First", + ], + "Second" => btreeset![ + "glob_reexport::Second", + "glob_reexport::Baz::Second", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn glob_of_glob_reexport() { + let test_crate = "glob_of_glob_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + "glob_of_glob_reexport::foo", + ], + "Bar" => btreeset![ + "glob_of_glob_reexport::Bar", + ], + "Baz" => btreeset![ + "glob_of_glob_reexport::Baz", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn glob_of_renamed_reexport() { + let test_crate = "glob_of_renamed_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + "glob_of_renamed_reexport::renamed_foo", + ], + "Bar" => btreeset![ + "glob_of_renamed_reexport::RenamedBar", + ], + "First" => btreeset![ + "glob_of_renamed_reexport::RenamedFirst", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn glob_reexport_enum_variants() { + let test_crate = "glob_reexport_enum_variants"; + let expected_items = btreemap! { + "First" => btreeset![ + "glob_reexport_enum_variants::First", + ], + "Second" => btreeset![ + "glob_reexport_enum_variants::Second", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn glob_reexport_cycle() { + let test_crate = "glob_reexport_cycle"; + let expected_items = btreemap! { + "foo" => btreeset![ + "glob_reexport_cycle::first::foo", + "glob_reexport_cycle::second::foo", + ], + "Bar" => btreeset![ + "glob_reexport_cycle::first::Bar", + "glob_reexport_cycle::second::Bar", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn infinite_recursive_reexport() { + let test_crate = "infinite_recursive_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + // We don't want to expand all infinitely-many names here. + // We only return cycle-free paths, which are the following: + "infinite_recursive_reexport::foo", + "infinite_recursive_reexport::inner::foo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn infinite_indirect_recursive_reexport() { + let test_crate = "infinite_indirect_recursive_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + // We don't want to expand all infinitely-many names here. + // We only return cycle-free paths, which are the following: + "infinite_indirect_recursive_reexport::foo", + "infinite_indirect_recursive_reexport::nested::foo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn infinite_corecursive_reexport() { + let test_crate = "infinite_corecursive_reexport"; + let expected_items = btreemap! { + "foo" => btreeset![ + // We don't want to expand all infinitely-many names here. + // We only return cycle-free paths, which are the following: + "infinite_corecursive_reexport::a::foo", + "infinite_corecursive_reexport::b::a::foo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_type_alias_reexport() { + let test_crate = "pub_type_alias_reexport"; + let expected_items = btreemap! { + "Foo" => btreeset![ + "pub_type_alias_reexport::Exported", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_generic_type_alias_reexport() { + let test_crate = "pub_generic_type_alias_reexport"; + let expected_items = btreemap! { + "Foo" => btreeset![ + // Only `Exported` and `ExportedRenamedParams` are re-exports. + // + //`ExportedRenamedParams` renames the generic parameters + // but does not change their meaning. + // + // `ExportedWithDefaults` is not a re-export because it adds + // + // The other type aliases are not equivalent since they constrain + // some of the underlying type's generic parameters. + "pub_generic_type_alias_reexport::Exported", + "pub_generic_type_alias_reexport::ExportedRenamedParams", + ], + "Exported" => btreeset![ + // The type alias itself is also a visible item. + "pub_generic_type_alias_reexport::Exported", + ], + "ExportedWithDefaults" => btreeset![ + // The type alias itself is also a visible item. + "pub_generic_type_alias_reexport::ExportedWithDefaults", + ], + "ExportedRenamedParams" => btreeset![ + // The type alias itself is also a visible item. + "pub_generic_type_alias_reexport::ExportedRenamedParams", + ], + "ExportedSpecificLifetime" => btreeset![ + "pub_generic_type_alias_reexport::ExportedSpecificLifetime", + ], + "ExportedSpecificType" => btreeset![ + "pub_generic_type_alias_reexport::ExportedSpecificType", + ], + "ExportedSpecificConst" => btreeset![ + "pub_generic_type_alias_reexport::ExportedSpecificConst", + ], + "ExportedFullySpecified" => btreeset![ + "pub_generic_type_alias_reexport::ExportedFullySpecified", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_generic_type_alias_shuffled_order() { + let test_crate = "pub_generic_type_alias_shuffled_order"; + let expected_items = btreemap! { + // The type aliases reverse the generic parameters' orders, + // so they are not re-exports of the underlying types. + "GenericFoo" => btreeset![ + "pub_generic_type_alias_shuffled_order::inner::GenericFoo", + ], + "LifetimeFoo" => btreeset![ + "pub_generic_type_alias_shuffled_order::inner::LifetimeFoo", + ], + "ConstFoo" => btreeset![ + "pub_generic_type_alias_shuffled_order::inner::ConstFoo", + ], + "ReversedGenericFoo" => btreeset![ + "pub_generic_type_alias_shuffled_order::ReversedGenericFoo", + ], + "ReversedLifetimeFoo" => btreeset![ + "pub_generic_type_alias_shuffled_order::ReversedLifetimeFoo", + ], + "ReversedConstFoo" => btreeset![ + "pub_generic_type_alias_shuffled_order::ReversedConstFoo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_generic_type_alias_added_defaults() { + let test_crate = "pub_generic_type_alias_added_defaults"; + let expected_items = btreemap! { + "Foo" => btreeset![ + "pub_generic_type_alias_added_defaults::inner::Foo", + ], + "Bar" => btreeset![ + "pub_generic_type_alias_added_defaults::inner::Bar", + ], + "DefaultFoo" => btreeset![ + "pub_generic_type_alias_added_defaults::DefaultFoo", + ], + "DefaultBar" => btreeset![ + "pub_generic_type_alias_added_defaults::DefaultBar", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_generic_type_alias_changed_defaults() { + let test_crate = "pub_generic_type_alias_changed_defaults"; + let expected_items = btreemap! { + // The type aliases change the default values of the generic parameters, + // so they are not re-exports of the underlying types. + "Foo" => btreeset![ + "pub_generic_type_alias_changed_defaults::inner::Foo", + ], + "Bar" => btreeset![ + "pub_generic_type_alias_changed_defaults::inner::Bar", + ], + "ExportedWithoutTypeDefault" => btreeset![ + "pub_generic_type_alias_changed_defaults::ExportedWithoutTypeDefault", + ], + "ExportedWithoutConstDefault" => btreeset![ + "pub_generic_type_alias_changed_defaults::ExportedWithoutConstDefault", + ], + "ExportedWithoutDefaults" => btreeset![ + "pub_generic_type_alias_changed_defaults::ExportedWithoutDefaults", + ], + "ExportedWithDifferentTypeDefault" => btreeset![ + "pub_generic_type_alias_changed_defaults::ExportedWithDifferentTypeDefault", + ], + "ExportedWithDifferentConstDefault" => btreeset![ + "pub_generic_type_alias_changed_defaults::ExportedWithDifferentConstDefault", + ], + "ExportedWithDifferentDefaults" => btreeset![ + "pub_generic_type_alias_changed_defaults::ExportedWithDifferentDefaults", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_generic_type_alias_same_signature_but_not_equivalent() { + let test_crate = "pub_generic_type_alias_same_signature_but_not_equivalent"; + let expected_items = btreemap! { + "GenericFoo" => btreeset![ + "pub_generic_type_alias_same_signature_but_not_equivalent::inner::GenericFoo", + ], + "ChangedFoo" => btreeset![ + "pub_generic_type_alias_same_signature_but_not_equivalent::ChangedFoo", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_type_alias_of_type_alias() { + let test_crate = "pub_type_alias_of_type_alias"; + let expected_items = btreemap! { + "Foo" => btreeset![ + "pub_type_alias_of_type_alias::inner::Foo", + "pub_type_alias_of_type_alias::inner::AliasedFoo", + "pub_type_alias_of_type_alias::ExportedFoo", + ], + "Bar" => btreeset![ + "pub_type_alias_of_type_alias::inner::Bar", + "pub_type_alias_of_type_alias::inner::AliasedBar", + "pub_type_alias_of_type_alias::ExportedBar", + ], + "AliasedFoo" => btreeset![ + "pub_type_alias_of_type_alias::inner::AliasedFoo", + "pub_type_alias_of_type_alias::ExportedFoo", + ], + "AliasedBar" => btreeset![ + "pub_type_alias_of_type_alias::inner::AliasedBar", + "pub_type_alias_of_type_alias::ExportedBar", + ], + "ExportedFoo" => btreeset![ + "pub_type_alias_of_type_alias::ExportedFoo", + ], + "ExportedBar" => btreeset![ + "pub_type_alias_of_type_alias::ExportedBar", + ], + "DifferentLifetimeBar" => btreeset![ + "pub_type_alias_of_type_alias::DifferentLifetimeBar", + ], + "DifferentGenericBar" => btreeset![ + "pub_type_alias_of_type_alias::DifferentGenericBar", + ], + "DifferentConstBar" => btreeset![ + "pub_type_alias_of_type_alias::DifferentConstBar", + ], + "ReorderedBar" => btreeset![ + "pub_type_alias_of_type_alias::ReorderedBar", + ], + "DefaultValueBar" => btreeset![ + "pub_type_alias_of_type_alias::DefaultValueBar", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } + + #[test] + fn pub_type_alias_of_composite_type() { + let test_crate = "pub_type_alias_of_composite_type"; + let expected_items = btreemap! { + "Foo" => btreeset![ + "pub_type_alias_of_composite_type::inner::Foo", + ], + "I64Tuple" => btreeset![ + "pub_type_alias_of_composite_type::I64Tuple", + ], + "MixedTuple" => btreeset![ + "pub_type_alias_of_composite_type::MixedTuple", + ], + "GenericTuple" => btreeset![ + "pub_type_alias_of_composite_type::GenericTuple", + ], + "LifetimeTuple" => btreeset![ + "pub_type_alias_of_composite_type::LifetimeTuple", + ], + "ConstTuple" => btreeset![ + "pub_type_alias_of_composite_type::ConstTuple", + ], + "DefaultGenericTuple" => btreeset![ + "pub_type_alias_of_composite_type::DefaultGenericTuple", + ], + "DefaultConstTuple" => btreeset![ + "pub_type_alias_of_composite_type::DefaultConstTuple", + ], + }; + + assert_exported_items_match(test_crate, &expected_items); + } } } diff --git a/test_crates/glob_of_glob_reexport/Cargo.toml b/test_crates/glob_of_glob_reexport/Cargo.toml new file mode 100644 index 0000000..2e1ec9a --- /dev/null +++ b/test_crates/glob_of_glob_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "glob_of_glob_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/glob_of_glob_reexport/src/lib.rs b/test_crates/glob_of_glob_reexport/src/lib.rs new file mode 100644 index 0000000..2676055 --- /dev/null +++ b/test_crates/glob_of_glob_reexport/src/lib.rs @@ -0,0 +1,22 @@ +//! This package exports the following: +//! - `foo` +//! - `Bar` +//! - `Baz` + +mod nested { + mod deeper { + pub fn foo() {} + + pub struct Bar; + + pub enum Baz { + First, + } + } + + pub(crate) mod sibling { + pub use super::deeper::*; + } +} + +pub use nested::sibling::*; diff --git a/test_crates/glob_of_renamed_reexport/Cargo.toml b/test_crates/glob_of_renamed_reexport/Cargo.toml new file mode 100644 index 0000000..86d55ee --- /dev/null +++ b/test_crates/glob_of_renamed_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "glob_of_renamed_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/glob_of_renamed_reexport/src/lib.rs b/test_crates/glob_of_renamed_reexport/src/lib.rs new file mode 100644 index 0000000..effc07f --- /dev/null +++ b/test_crates/glob_of_renamed_reexport/src/lib.rs @@ -0,0 +1,24 @@ +//! This package exports the following: +//! - `renamed_foo` +//! - `RenamedBar` +//! - `RenamedFirst` + +mod nested { + mod deeper { + pub fn foo() {} + + pub struct Bar; + + pub enum Baz { + First, + } + } + + pub(crate) mod renaming { + pub use super::deeper::foo as renamed_foo; + pub use super::deeper::Bar as RenamedBar; + pub use super::deeper::Baz::First as RenamedFirst; + } +} + +pub use nested::renaming::*; diff --git a/test_crates/glob_reexport/Cargo.toml b/test_crates/glob_reexport/Cargo.toml new file mode 100644 index 0000000..fe0ae41 --- /dev/null +++ b/test_crates/glob_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "glob_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/glob_reexport/src/lib.rs b/test_crates/glob_reexport/src/lib.rs new file mode 100644 index 0000000..32b3ce3 --- /dev/null +++ b/test_crates/glob_reexport/src/lib.rs @@ -0,0 +1,27 @@ +//! This package exports the following: +//! - `foo`, also as `inner::foo` +//! - `Bar`, also as `inner::Bar` +//! - `nested` +//! - `Baz` (due to the glob import) +//! - `First` +//! - `Second` + +pub mod inner { + pub fn foo() {} + + pub struct Bar; +} + +pub use inner::*; + +pub mod nested { + pub(crate) mod deeper { + pub enum Baz { + First, + Second, + } + } +} + +pub use nested::deeper::Baz::*; +pub use nested::deeper::*; diff --git a/test_crates/glob_reexport_cycle/Cargo.toml b/test_crates/glob_reexport_cycle/Cargo.toml new file mode 100644 index 0000000..cfdca24 --- /dev/null +++ b/test_crates/glob_reexport_cycle/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "glob_reexport_cycle" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/glob_reexport_cycle/src/lib.rs b/test_crates/glob_reexport_cycle/src/lib.rs new file mode 100644 index 0000000..3a7cc66 --- /dev/null +++ b/test_crates/glob_reexport_cycle/src/lib.rs @@ -0,0 +1,15 @@ +//! This package exports the following: +//! - `first::foo`, also as `second::foo` +//! - `second::Bar`, also as `first::Bar` + +pub mod first { + pub fn foo() {} + + pub use crate::second::*; +} + +pub mod second { + pub struct Bar; + + pub use crate::first::*; +} diff --git a/test_crates/glob_reexport_enum_variants/Cargo.toml b/test_crates/glob_reexport_enum_variants/Cargo.toml new file mode 100644 index 0000000..e442dd4 --- /dev/null +++ b/test_crates/glob_reexport_enum_variants/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "glob_reexport_enum_variants" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/glob_reexport_enum_variants/src/lib.rs b/test_crates/glob_reexport_enum_variants/src/lib.rs new file mode 100644 index 0000000..614b292 --- /dev/null +++ b/test_crates/glob_reexport_enum_variants/src/lib.rs @@ -0,0 +1,12 @@ +//! This package exports the following: +//! - `First` +//! - `Second` + +mod nested { + pub enum Foo { + First, + Second, + } +} + +pub use crate::nested::Foo::*; diff --git a/test_crates/infinite_corecursive_reexport/Cargo.toml b/test_crates/infinite_corecursive_reexport/Cargo.toml new file mode 100644 index 0000000..21eaa4a --- /dev/null +++ b/test_crates/infinite_corecursive_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "infinite_corecursive_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/infinite_corecursive_reexport/src/lib.rs b/test_crates/infinite_corecursive_reexport/src/lib.rs new file mode 100644 index 0000000..0c51933 --- /dev/null +++ b/test_crates/infinite_corecursive_reexport/src/lib.rs @@ -0,0 +1,21 @@ +//! The below function should be available as: +//! - `a::foo()` +//! - `b::a::foo()` +//! - `a::b::a::foo()` +//! - any sequence that flips between `a` and `b`, and ends with `foo()` +//! +//! However, we don't actually want to expand the names infinitely. +//! For the purposes of our import tracking, we'll consider only +//! cycle-free import paths, which are the following: +//! - `a::foo()` +//! - `b::a::foo()` + +pub mod a { + pub use crate::b; + + pub fn foo() {} +} + +pub mod b { + pub use crate::a; +} diff --git a/test_crates/infinite_indirect_recursive_reexport/Cargo.toml b/test_crates/infinite_indirect_recursive_reexport/Cargo.toml new file mode 100644 index 0000000..2df1c89 --- /dev/null +++ b/test_crates/infinite_indirect_recursive_reexport/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "infinite_indirect_recursive_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/infinite_indirect_recursive_reexport/src/lib.rs b/test_crates/infinite_indirect_recursive_reexport/src/lib.rs new file mode 100644 index 0000000..a399b79 --- /dev/null +++ b/test_crates/infinite_indirect_recursive_reexport/src/lib.rs @@ -0,0 +1,25 @@ +//! The below function should be available as: +//! - `foo()` +//! - `nested::foo()` +//! - `nested::nested::foo()` +//! - any number of consecutive `nested` followed by `foo()` +//! +//! However, we don't actually want to expand the names infinitely. +//! For the purposes of our import tracking, we'll consider only +//! cycle-free import paths, which are the following: +//! - `foo()` +//! - `nested::foo()` + +mod nested_other { + pub use crate::nested; + + pub fn foo() {} +} + +pub mod nested { + pub use crate::nested_other::foo; + + pub use crate::nested_other::nested; +} + +pub use nested::foo; diff --git a/test_crates/infinite_recursive_reexport/Cargo.toml b/test_crates/infinite_recursive_reexport/Cargo.toml new file mode 100644 index 0000000..8891810 --- /dev/null +++ b/test_crates/infinite_recursive_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "infinite_recursive_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/infinite_recursive_reexport/src/lib.rs b/test_crates/infinite_recursive_reexport/src/lib.rs new file mode 100644 index 0000000..c8f470e --- /dev/null +++ b/test_crates/infinite_recursive_reexport/src/lib.rs @@ -0,0 +1,19 @@ +//! The below function should be available as: +//! - `foo()` +//! - `inner::foo()` +//! - `inner::inner::foo()` +//! - any number of consecutive `inner` followed by `foo()` +//! +//! However, we don't actually want to expand the names infinitely. +//! For the purposes of our import tracking, we'll consider only +//! cycle-free import paths, which are the following: +//! - `foo()` +//! - `inner::foo()` + +pub mod inner { + pub fn foo() {} + + pub use super::inner; +} + +pub use crate::inner::foo; diff --git a/test_crates/pub_generic_type_alias_added_defaults/Cargo.toml b/test_crates/pub_generic_type_alias_added_defaults/Cargo.toml new file mode 100644 index 0000000..b925184 --- /dev/null +++ b/test_crates/pub_generic_type_alias_added_defaults/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_generic_type_alias_added_defaults" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_generic_type_alias_added_defaults/src/lib.rs b/test_crates/pub_generic_type_alias_added_defaults/src/lib.rs new file mode 100644 index 0000000..7c65e5b --- /dev/null +++ b/test_crates/pub_generic_type_alias_added_defaults/src/lib.rs @@ -0,0 +1,25 @@ +//! This package exports the following: +//! - `inner::Foo` +//! - `inner::Bar` +//! - `DefaultFoo` +//! - `DefaultBar` +//! +//! Adding a default generic parameter value in the typedef makes it no longer +//! equivalent to the underlying, and not equivalent to a `pub use`. +//! It cannot be treated as a re-export of the underlying type. + +pub mod inner { + pub struct Foo<'a, T, const N: usize> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } + + pub struct Bar<'a, const N: usize, T> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } +} + +pub type DefaultFoo<'a, T, const N: usize = 5> = crate::inner::Foo<'a, T, N>; + +pub type DefaultBar<'a, const N: usize, T = i64> = crate::inner::Bar<'a, N, T>; diff --git a/test_crates/pub_generic_type_alias_changed_defaults/Cargo.toml b/test_crates/pub_generic_type_alias_changed_defaults/Cargo.toml new file mode 100644 index 0000000..145b2ea --- /dev/null +++ b/test_crates/pub_generic_type_alias_changed_defaults/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_generic_type_alias_changed_defaults" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_generic_type_alias_changed_defaults/src/lib.rs b/test_crates/pub_generic_type_alias_changed_defaults/src/lib.rs new file mode 100644 index 0000000..6960697 --- /dev/null +++ b/test_crates/pub_generic_type_alias_changed_defaults/src/lib.rs @@ -0,0 +1,36 @@ +//! This package does *not* include any re-exports of `inner::Foo` and `inner::Bar`. +//! +//! All type aliases change the semantics of the underlying type's generic parameters, +//! which disqualifies them from being equivalent to re-exports. This is due to: +//! - omitting default values for generic parameters +//! - changing default values for generic parameters +//! +//! All type aliases are public and are exported. + +pub mod inner { + pub struct Foo<'a, T = i64, const N: usize = 5> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } + + pub struct Bar<'a, const N: usize = 5, T = i64> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } +} + +pub type ExportedWithoutTypeDefault<'a, T, const N: usize = 5> = crate::inner::Foo<'a, T, N>; + +// This uses `Bar` instead of `Foo` because generic parameters with defaults must be trailing. +pub type ExportedWithoutConstDefault<'a, const N: usize, T = i64> = crate::inner::Bar<'a, N, T>; + +pub type ExportedWithoutDefaults<'a, T, const N: usize> = crate::inner::Foo<'a, T, N>; + +pub type ExportedWithDifferentTypeDefault<'a, T = usize, const N: usize = 5> = + crate::inner::Foo<'a, T, N>; + +pub type ExportedWithDifferentConstDefault<'a, T = i64, const N: usize = 10> = + crate::inner::Foo<'a, T, N>; + +pub type ExportedWithDifferentDefaults<'a, T = usize, const N: usize = 10> = + crate::inner::Foo<'a, T, N>; diff --git a/test_crates/pub_generic_type_alias_reexport/Cargo.toml b/test_crates/pub_generic_type_alias_reexport/Cargo.toml new file mode 100644 index 0000000..4e863b2 --- /dev/null +++ b/test_crates/pub_generic_type_alias_reexport/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_generic_type_alias_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_generic_type_alias_reexport/src/lib.rs b/test_crates/pub_generic_type_alias_reexport/src/lib.rs new file mode 100644 index 0000000..7791ef9 --- /dev/null +++ b/test_crates/pub_generic_type_alias_reexport/src/lib.rs @@ -0,0 +1,62 @@ +//! This package exports the following renames of `Foo` (which itself is not directly visible): +//! - `Exported<'a, T, const N: usize>` +//! - `ExportedRenamedParams<'b, U, const M: usize>` +//! +//! The following type aliases are also exported but do not qualify +//! as renames since they do not leave Foo's generic parameters unmodified: +//! - `ExportedWithDefaults<'a, T = i64, const N: usize = 5>` +//! - `ExportedSpecificLifetime` +//! - `ExportedSpecificType<'a, const N: usize>` +//! - `ExportedSpecificConst<'a, T>` +//! - `ExportedFullySpecified` +//! +//! The `inner` module is private so `Foo` is not exported. +//! However, all type aliases are public and are exported. +//! The `Foo::bar` function is also visible, via the renames of `Foo`. +//! +//! As far as anyone outside this crate can tell, +//! the `Foo` type is actually called `Exported` or `ExportedRenamedParams` now. + +mod inner { + pub struct Foo<'a, T, const N: usize> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } + + impl<'a, T, const N: usize> Foo<'a, T, N> { + pub fn bar() {} + } +} + +// The following type aliases are valid re-exports. +// They do not change the meaning of the generic parameters, even while renaming them +// or supplying default values. + +pub type Exported<'a, T, const N: usize> = crate::inner::Foo<'a, T, N>; + +pub type ExportedRenamedParams<'b, U, const M: usize> = crate::inner::Foo<'b, U, M>; + +// This type alias is not a re-export, since it adds default values for the generic parameters. +// This is not achievable with a `pub use`, so it cannot be a re-export. +pub type ExportedWithDefaults<'a, T = i64, const N: usize = 5> = crate::inner::Foo<'a, T, N>; + +// The following type aliases are not valid re-exports, +// since they constrain the underlying type's generic parameters. + +pub type ExportedSpecificLifetime = crate::inner::Foo<'static, T, N>; + +pub type ExportedSpecificType<'a, const N: usize> = crate::inner::Foo<'a, i64, N>; + +pub type ExportedSpecificConst<'a, T> = crate::inner::Foo<'a, T, 5>; + +pub type ExportedFullySpecified = crate::inner::Foo<'static, i64, 5>; + +// Bounds on type aliases are not checked. +// The following line triggers the rustc `type_alias_bounds` warning, +// and rustc suggests removing the bound. +// +// It's plausible that in the future, this may become a hard error, +// so we won't mandate a particular behavior on it in our library. +// ``` +// pub type ExportedWithWhereBound<'a, T: Into, const N: usize> = crate::inner::Foo<'a, T, N>; +// ``` diff --git a/test_crates/pub_generic_type_alias_same_signature_but_not_equivalent/Cargo.toml b/test_crates/pub_generic_type_alias_same_signature_but_not_equivalent/Cargo.toml new file mode 100644 index 0000000..723aeb7 --- /dev/null +++ b/test_crates/pub_generic_type_alias_same_signature_but_not_equivalent/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_generic_type_alias_same_signature_but_not_equivalent" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_generic_type_alias_same_signature_but_not_equivalent/src/lib.rs b/test_crates/pub_generic_type_alias_same_signature_but_not_equivalent/src/lib.rs new file mode 100644 index 0000000..85ce784 --- /dev/null +++ b/test_crates/pub_generic_type_alias_same_signature_but_not_equivalent/src/lib.rs @@ -0,0 +1,32 @@ +//! This package exports the following: +//! - `inner::GenericFoo` +//! - `ChangedFoo` +//! +//! However, the `pub type ChangedFoo` is not a re-export of its underlying type, +//! since it changes the meaning of the generic parameters relative to the underlying type. +//! This is not consistent with a re-export, and cannot be achieved using a `pub use` statement. + +pub mod inner { + pub struct GenericFoo { + _marker: std::marker::PhantomData<(A, B)>, + } +} + +// The below type aliases include the same generic parameters as the underlying types, +// but do not use them in a way that is equivalent to the underlying type. +// +// Keeping the names the same is intentional, if confusing: +// it makes sure that we compare the *meaning* of the generic type parameter +// rather than just its name relative to its underlying definition. + +pub type ChangedFoo = crate::inner::GenericFoo<(A, B), i64>; + +// TODO: when generic const expressions become stable, include a test case like: +// ```rust +// pub struct ConstBar { +// _a: [i64; A], +// _b: [i64; B], +// } +// +// pub type ChangedBar = crate::ConstBar; +// ``` diff --git a/test_crates/pub_generic_type_alias_shuffled_order/Cargo.toml b/test_crates/pub_generic_type_alias_shuffled_order/Cargo.toml new file mode 100644 index 0000000..f28c38c --- /dev/null +++ b/test_crates/pub_generic_type_alias_shuffled_order/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_generic_type_alias_shuffled_order" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_generic_type_alias_shuffled_order/src/lib.rs b/test_crates/pub_generic_type_alias_shuffled_order/src/lib.rs new file mode 100644 index 0000000..f233a92 --- /dev/null +++ b/test_crates/pub_generic_type_alias_shuffled_order/src/lib.rs @@ -0,0 +1,39 @@ +//! This package exports the following: +//! - `inner::GenericFoo` +//! - `inner::LifetimeFoo<'a, 'b>` +//! - `inner::ConstFoo` +//! - `ReversedGenericFoo` +//! - `ReversedLifetimeFoo<'a, 'a>` +//! - `ReversedConstFoo` +//! +//! However, these exported types are not considered re-exports of the underlying types! +//! Each of them has the generic parameter order flipped relative to the underlying type, +//! which results in a conceptually new type. This is not a re-export, and cannot be achieved +//! using a `pub use` statement. + +pub mod inner { + pub struct GenericFoo { + _marker: std::marker::PhantomData<(A, B)>, + } + + pub struct LifetimeFoo<'a, 'b> { + _marker: std::marker::PhantomData<&'a &'b ()>, + } + + pub struct ConstFoo { + _a: [i64; A], + _b: [i64; B], + } +} + +// In the below type aliases, the generic parameters keep the same names +// but reverse their order relative to the underlying items. +// Keeping the names the same is intentional, if confusing: +// it makes sure that we compare the *meaning* of the generic type parameter +// rather than just its name relative to its underlying definition. + +pub type ReversedGenericFoo = crate::inner::GenericFoo; + +pub type ReversedLifetimeFoo<'a, 'b> = crate::inner::LifetimeFoo<'b, 'a>; + +pub type ReversedConstFoo = crate::inner::ConstFoo; diff --git a/test_crates/pub_inside_pub_crate_mod/Cargo.toml b/test_crates/pub_inside_pub_crate_mod/Cargo.toml new file mode 100644 index 0000000..9ce8e2b --- /dev/null +++ b/test_crates/pub_inside_pub_crate_mod/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_inside_pub_crate_mod" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_inside_pub_crate_mod/src/lib.rs b/test_crates/pub_inside_pub_crate_mod/src/lib.rs new file mode 100644 index 0000000..66bb61a --- /dev/null +++ b/test_crates/pub_inside_pub_crate_mod/src/lib.rs @@ -0,0 +1,5 @@ +pub(crate) mod nested { + pub struct Foo; +} + +pub struct Bar; diff --git a/test_crates/pub_type_alias_of_composite_type/Cargo.toml b/test_crates/pub_type_alias_of_composite_type/Cargo.toml new file mode 100644 index 0000000..d7dd387 --- /dev/null +++ b/test_crates/pub_type_alias_of_composite_type/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_type_alias_of_composite_type" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_type_alias_of_composite_type/src/lib.rs b/test_crates/pub_type_alias_of_composite_type/src/lib.rs new file mode 100644 index 0000000..4d1060d --- /dev/null +++ b/test_crates/pub_type_alias_of_composite_type/src/lib.rs @@ -0,0 +1,23 @@ +//! None of the `pub type` uses here are equivalent to a `pub use`, +//! so all the types and type aliases here are individually exported. + +pub mod inner { + pub struct Foo<'a, T, const N: usize> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } +} + +pub type I64Tuple = (i64, i64); + +pub type MixedTuple = (i64, inner::Foo<'static, bool, 5>); + +pub type GenericTuple = (inner::Foo<'static, T, 5>, i64); + +pub type LifetimeTuple<'a> = (inner::Foo<'a, bool, 5>, i64); + +pub type ConstTuple = (inner::Foo<'static, bool, N>, i64); + +pub type DefaultGenericTuple = (inner::Foo<'static, T, 5>, i64); + +pub type DefaultConstTuple = (inner::Foo<'static, bool, N>, i64); diff --git a/test_crates/pub_type_alias_of_type_alias/Cargo.toml b/test_crates/pub_type_alias_of_type_alias/Cargo.toml new file mode 100644 index 0000000..b8fb21c --- /dev/null +++ b/test_crates/pub_type_alias_of_type_alias/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_type_alias_of_type_alias" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_type_alias_of_type_alias/src/lib.rs b/test_crates/pub_type_alias_of_type_alias/src/lib.rs new file mode 100644 index 0000000..d6b06dc --- /dev/null +++ b/test_crates/pub_type_alias_of_type_alias/src/lib.rs @@ -0,0 +1,35 @@ +//! This package exports the following: +//! - `inner::Foo`, also as `inner::AliasedFoo` and `ExportedFoo` +//! - `inner::Bar`, also as `inner::AliasedBar` and `ExportedBar` +//! - `inner::AliasedFoo`, also as `ExportedFoo` +//! - `inner::AliasedBar`, also as `ExportedBar` +//! - `ExportedFoo` +//! - `ExportedBar` +//! - `DifferentLifetimeBar` +//! - `DifferentGenericBar` +//! - `DifferentConstBar` +//! - `ReorderedBar` +//! - `DefaultValueBar` + +pub mod inner { + pub struct Foo; + + pub struct Bar<'a, T, const N: usize> { + _marker: std::marker::PhantomData<&'a T>, + _n: [i64; N], + } + + pub type AliasedFoo = Foo; + pub type AliasedBar<'a, T, const N: usize> = Bar<'a, T, N>; +} + +pub type ExportedFoo = crate::inner::AliasedFoo; +pub type ExportedBar<'a, T, const N: usize> = crate::inner::AliasedBar<'a, T, N>; + +// The following type aliases change the generics relative to `AliasedBar`, and are not re-exports. + +pub type DifferentLifetimeBar = crate::inner::AliasedBar<'static, T, N>; +pub type DifferentGenericBar<'a, const N: usize> = crate::inner::AliasedBar<'a, i64, N>; +pub type DifferentConstBar<'a, T> = crate::inner::AliasedBar<'a, T, 5>; +pub type ReorderedBar<'a, const T: usize, N> = crate::inner::AliasedBar<'a, N, T>; +pub type DefaultValueBar<'a, T, const N: usize = 7> = crate::inner::AliasedBar<'a, T, N>; diff --git a/test_crates/pub_type_alias_reexport/Cargo.toml b/test_crates/pub_type_alias_reexport/Cargo.toml new file mode 100644 index 0000000..82d2705 --- /dev/null +++ b/test_crates/pub_type_alias_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "pub_type_alias_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_type_alias_reexport/src/lib.rs b/test_crates/pub_type_alias_reexport/src/lib.rs new file mode 100644 index 0000000..0fb51f7 --- /dev/null +++ b/test_crates/pub_type_alias_reexport/src/lib.rs @@ -0,0 +1,15 @@ +//! This package exports the following: +//! - Exported +//! +//! The `inner` module is private so `Foo` is not exported. +//! However, the `Exported` type alias is public and is exported. +//! The `Exported::bar` function is also visible (but not directly exported). +//! +//! As far as anyone outside this crate can tell, +//! the `Foo` type is actually called `Exported` now. + +mod inner { + pub struct Foo; +} + +pub type Exported = crate::inner::Foo; diff --git a/test_crates/pub_type_alias_shuffled_order/Cargo.toml b/test_crates/pub_type_alias_shuffled_order/Cargo.toml new file mode 100644 index 0000000..486f867 --- /dev/null +++ b/test_crates/pub_type_alias_shuffled_order/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pub_type_alias_shuffled_order" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/pub_type_alias_shuffled_order/src/lib.rs b/test_crates/pub_type_alias_shuffled_order/src/lib.rs new file mode 100644 index 0000000..3f99528 --- /dev/null +++ b/test_crates/pub_type_alias_shuffled_order/src/lib.rs @@ -0,0 +1,38 @@ +//! This package exports the following: +//! - `ReversedGenericFoo` +//! - `ReversedLifetimeFoo<'x, 'y>` +//! - `ReversedConstFoo` +//! +//! However, these exported types are not considered re-exports of the underlying types! +//! Each of them has the generic parameter order flipped relative to the underlying type, +//! which results in a conceptually new type. This is not a re-export, and cannot be achieved +//! using a `pub use` statement. +//! +//! The `inner` module is private so the underlying types are not exported. + +mod inner { + pub struct GenericFoo { + _marker: std::marker::PhantomData<(A, B)>, + } + + pub struct LifetimeFoo<'a, 'b> { + _marker: std::marker::PhantomData<&'a &'b ()>, + } + + pub struct ConstFoo { + _a: [i64; A], + _b: [i64; B], + } +} + +// In the below type aliases, the generic parameters keep the same names +// but reverse their order relative to the underlying items. +// Keeping the names the same is intentional, if confusing: +// it makes sure that we compare the *meaning* of the generic type parameter +// rather than just its name relative to its underlying definition. + +pub type ReversedGenericFoo = crate::inner::GenericFoo; + +pub type ReversedLifetimeFoo<'a, 'b> = crate::inner::LifetimeFoo<'b, 'a>; + +pub type ReversedConstFoo = crate::inner::ConstFoo; diff --git a/test_crates/reexport/Cargo.toml b/test_crates/reexport/Cargo.toml new file mode 100644 index 0000000..5b97475 --- /dev/null +++ b/test_crates/reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/reexport/src/lib.rs b/test_crates/reexport/src/lib.rs new file mode 100644 index 0000000..c40e07a --- /dev/null +++ b/test_crates/reexport/src/lib.rs @@ -0,0 +1,9 @@ +//! The below function should be available as both: +//! - `inner::foo()` +//! - `foo()` + +pub mod inner { + pub fn foo() {} +} + +pub use crate::inner::foo; diff --git a/test_crates/reexport_from_private_module/Cargo.toml b/test_crates/reexport_from_private_module/Cargo.toml new file mode 100644 index 0000000..ba26a5f --- /dev/null +++ b/test_crates/reexport_from_private_module/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "reexport_from_private_module" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/reexport_from_private_module/src/lib.rs b/test_crates/reexport_from_private_module/src/lib.rs new file mode 100644 index 0000000..686de21 --- /dev/null +++ b/test_crates/reexport_from_private_module/src/lib.rs @@ -0,0 +1,25 @@ +//! This package exports the following: +//! - `foo` +//! - `Bar` +//! - `nested::Baz` +//! - `quux` +//! +//! The `inner` and `inner2` modules are private but the re-exports expose their contents. + +mod inner { + pub fn foo() {} + + pub struct Bar; + + pub mod nested { + pub struct Baz; + } +} + +pub use inner::*; + +mod inner2 { + pub fn quux() {} +} + +pub use inner2::quux; diff --git a/test_crates/renaming_mod_reexport/Cargo.toml b/test_crates/renaming_mod_reexport/Cargo.toml new file mode 100644 index 0000000..136e35a --- /dev/null +++ b/test_crates/renaming_mod_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "renaming_mod_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/renaming_mod_reexport/src/lib.rs b/test_crates/renaming_mod_reexport/src/lib.rs new file mode 100644 index 0000000..242a691 --- /dev/null +++ b/test_crates/renaming_mod_reexport/src/lib.rs @@ -0,0 +1,14 @@ +//! The below function should be available as: +//! - `inner::a::foo()` +//! - `inner::b::foo()` +//! - `direct::foo()` + +pub mod inner { + pub mod a { + pub fn foo() {} + } + + pub use self::a as b; +} + +pub use crate::inner::b as direct; diff --git a/test_crates/renaming_reexport/Cargo.toml b/test_crates/renaming_reexport/Cargo.toml new file mode 100644 index 0000000..def4071 --- /dev/null +++ b/test_crates/renaming_reexport/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "renaming_reexport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/renaming_reexport/src/lib.rs b/test_crates/renaming_reexport/src/lib.rs new file mode 100644 index 0000000..447456b --- /dev/null +++ b/test_crates/renaming_reexport/src/lib.rs @@ -0,0 +1,9 @@ +//! The below function should be available as both: +//! - `inner::foo()` +//! - `bar()` + +pub mod inner { + pub fn foo() {} +} + +pub use crate::inner::foo as bar;